---
layout: blank
hide-in-nav: true
---
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>H2 | Chase</title>
  <style>
    *,
    html,
    body {
      margin: 0;
      padding: 0;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
    }

    html,
    body {
      width: 100%;
      height: 100%;
    }

    main {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .grid-container {
      display: grid;
      grid-template-rows: repeat(3, 1fr);
      grid-template-columns: repeat(3, 1fr);
      width: 600px;
      height: 600px;
      border: solid 10px silver;
      box-shadow: 10px 10px 20px rgba(0, 0, 0, 0.5) inset, 10px 10px 20px rgba(0, 0, 0, 0.5);
      padding: 10px;
    }

    .grid-container .cell {
      width: 200px;
      height: 200px;
      position: relative;
      transition: 0.15s;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .grid-container .cell span {
      font-size: 36px;
      color: white;
    }

    .test {
      position: relative;
      left: 0;
      top: 0;
      width: 200px;
      height: 200px;
      border: solid 2px plum;
    }
  </style>
  <script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>

<body>
  <main>
    <div id="app" class="grid-container">
      <section id="0" class="cell" @mousedown="move" style="background: #2ecc71;"><span>1</span></section>
      <section id="1" class="cell" @mousedown="move" style="background: #e67e22;"><span>2</span></section>
      <section id="2" class="cell" @mousedown="move" style="background: #3498db;"><span>3</span></section>
      <section id="3" class="cell" @mousedown="move" style="background: #8e44ad;"><span>4</span></section>
      <section id="4" class="cell" @mousedown="move" style="background: #c44569;"><span>5</span></section>
      <section id="5" class="cell" @mousedown="move" style="background: #f1c40f;"><span>6</span></section>
      <section id="6" class="cell" @mousedown="move" style="background: #a29bfe;"><span>7</span></section>
      <section id="7" class="cell" @mousedown="move" style="background: #81ecec;"><span>8</span></section>
    </div>
  </main>
  <script>
    // 全局常量，表示上下左右
    const GLOBAL = {
      UP: 0,
      RIGHT: 1,
      DOWN: 2,
      LEFT: 3,
      NON_EMPTY: -1
    };

    let app = new Vue({
      el: '#app',
      data: {
        // 空位的位置
        emptyPos: [2, 2],
        // 滑块的方位，[0]为row数，[1]为column数
        positions: [
          [0, 0],
          [0, 1],
          [0, 2],
          [1, 0],
          [1, 1],
          [1, 2],
          [2, 0],
          [2, 1],
        ]
      },
      methods: {
        // 鼠标按下时触发
        move(e) {
          let o = e.target;
          // 获取当前滑块的index
          let index = parseInt(o.id);
          // 获取空位位于此滑块的方位
          let direction = this.emptyDirection(index);

          // 获取元素的宽度
          let w = o.clientWidth;

          // 鼠标按下时的起始X,Y坐标
          let fromX = e.clientX;
          let fromY = e.clientY;

          // 获取划款当前的position偏移量
          let leftString = o.style.left || "0px";
          let topString = o.style.top || "0px";
          let originLeft = parseInt(leftString.substring(0, leftString.length - 2));
          let originTop = parseInt(topString.substring(0, topString.length - 2));

          // 如果该滑块周围有空位，则可以滑动。设置滑动事件
          if (direction != GLOBAL.NON_EMPTY) {
            document.onmousemove = (e) => {
              // 计算鼠标滑动的距离
              let deltaLeft = e.clientX - fromX;
              let deltaTop = e.clientY - fromY;

              // 如果滑动距离超过滑块宽度，就不再滑动
              // 表现为只能滑动一格
              if (Math.abs(deltaLeft) >= w || Math.abs(deltaTop) >= w) return;

              // 定义两个函数
              let positived = (num) => num < 0 ? 0 : num;
              let negatived = (num) => num > 0 ? 0 : num;

              // 根据空位的方位，设置滑块跟随鼠标运动
              if (direction == GLOBAL.LEFT) {
                o.style.left = (originLeft + negatived(deltaLeft)) + 'px';
              } else if (direction == GLOBAL.RIGHT) {
                o.style.left = (originLeft + positived(deltaLeft)) + 'px';
              } else if (direction == GLOBAL.UP) {
                o.style.top = (originTop + negatived(deltaTop)) + 'px';
              } else {
                o.style.top = (originTop + positived(deltaTop)) + 'px';
              }
            };

            document.onmouseup = (e) => {
              document.onmousemove = null;
              document.onmouseup = null;
              // 计算鼠标滑动的距离
              let deltaLeft = e.clientX - fromX;
              let deltaTop = e.clientY - fromY;

              // 根据空位的方位，设置鼠标释放时滑块的最终位置
              // 如果鼠标滑动的距离小于滑块宽度的一半则不滑动
              // 否则滑动到目标位置
              // 若滑动，则更新空位和滑块的方位
              if (direction == GLOBAL.LEFT) {
                if (deltaLeft <= 0 && Math.abs(deltaLeft) >= w / 2) {
                  o.style.left = (originLeft - w) + 'px';
                  this.emptyPos = this.getMovedPosition(this.emptyPos, GLOBAL.RIGHT);
                  this.positions[index] = this.getMovedPosition(this.positions[index], GLOBAL.LEFT);
                } else {
                  o.style.left = originLeft + 'px';
                }
              } else if (direction == GLOBAL.RIGHT) {
                if (deltaLeft >= 0 && Math.abs(deltaLeft) >= w / 2) {
                  o.style.left = (originLeft + w) + 'px';
                  this.emptyPos = this.getMovedPosition(this.emptyPos, GLOBAL.LEFT);
                  this.positions[index] = this.getMovedPosition(this.positions[index], GLOBAL.RIGHT);
                } else {
                  o.style.left = originLeft + 'px';
                }
              } else if (direction == GLOBAL.UP) {
                if (deltaTop <= 0 && Math.abs(deltaTop) >= w / 2) {
                  o.style.top = (originTop - w) + 'px';
                  this.emptyPos = this.getMovedPosition(this.emptyPos, GLOBAL.DOWN);
                  this.positions[index] = this.getMovedPosition(this.positions[index], GLOBAL.UP);
                } else {
                  o.style.top = originTop + 'px';
                }
              } else {
                if (deltaTop >= 0 && Math.abs(deltaTop) >= w / 2) {
                  o.style.top = (originTop + w) + 'px';
                  this.emptyPos = this.getMovedPosition(this.emptyPos, GLOBAL.UP);
                  this.positions[index] = this.getMovedPosition(this.positions[index], GLOBAL.DOWN);
                } else {
                  o.style.top = originTop + 'px';
                }
              }
            }
          }
        },
        // 计算空位位于index滑块的方位，返回上下左右（见GLOBAL中的常量）
        emptyDirection(index) {
          let emptyPos = this.emptyPos;
          let position = this.positions[index];
          // console.log(`${position[0]}, ${position[1]}`);

          if (position[0] - 1 == emptyPos[0] && position[1] == emptyPos[1]) {
            return GLOBAL.UP;
          } else if (position[0] + 1 == emptyPos[0] && position[1] == emptyPos[1]) {
            return GLOBAL.DOWN;
          } else if (position[1] - 1 == emptyPos[1] && position[0] == emptyPos[0]) {
            return GLOBAL.LEFT;
          } else if (position[1] + 1 == emptyPos[1] && position[0] == emptyPos[0]) {
            return GLOBAL.RIGHT;
          } else {
            return GLOBAL.NON_EMPTY;
          }
        },

        // 返回移动之后的元素位置
        // p为position，是一个数组，第一个元素是row，第二个是column
        // d为移动方位，取值为GLOBAL中定义的方向
        getMovedPosition(p, d) {
          if (d == GLOBAL.LEFT) {
            return [p[0], p[1] - 1];
          } else if (d == GLOBAL.RIGHT) {
            return [p[0], p[1] + 1];
          } else if (d == GLOBAL.UP) {
            return [p[0] - 1, p[1]];
          } else {
            return [p[0] + 1, p[1]];
          }
        }
      }
    });
  </script>
</body>

</html>